Pandas是在NumPy基础上建立的新程序库,提供了一种高效的 DataFrame数据结构。 DataFrame本质上是一种带行标签和列标签、支持相同类型数据和缺失值的多维数组。 Pandas不仅为带各种标签的数据提供了便利的存储界面,还实现了许多强大的操作,这些操作对数据库框架和电子表格程序的用户来说非常熟悉。

建立在NumPy数组结构上的 Pandas,尤其是它的 Series和 DataFrame对象,为数据科学家们处理那些消耗大量时间的“数据清理”(data munging)任务提供了捷径。

安装并使用Pandas

在安装Pandas之前,确保操作系统中有NumPy。

Pandas对象简介

如果从底层视角观察Pandas对象,可以把它们看成增强版的NumPy结构化数组,行列都不再只是简单的整数索引,还可以带上标签。Pandas的三个基本数据结构:Series、DataFrame和Index。

Pandas的Series对象

Pandas的 Series对象是一个带索引数据构成的一维数组。可以用一个数组创建 Series对象。


从上面的结果中,你会发现 Series对象将一组数据和一组索引绑定在一起,我们可以通过 values属性和 index属性获取数据。 values属性返回的结果与 NumPy数组类似。

index属性返回的结果是一个类型为pd. Index的类数组对象。

和 NumPy数组一样,数据可以通过 Python的中括号索引标签获取。

Pandas的 Series对象比它模仿的一维 NumPy数组更加通用、灵活。

Serise是通用的 NumPy数组

Series对象和一维 NumPy数组两者间的本质差异其实是索引: NumPy数组通
过隐式定义的整数索引获取数值,而 Pandas的 Series对象用一种显式定义的索引与数值关联。显式索引的定义让 Series对象拥有了更强的能力。例如,索引不
再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以用字符串定义索引。也可以使用不连续或不按顺序的索引。


Series是特殊的字典

可以把 Pandas的 Series对象看成一种特殊的 Python字典。字典是一种将任意键映射到一组任意值的数据结构,而 Series对象其实是一种将类型键映射到一组类型值的数据结构。类型至关重要:就像 NumPy数组背后特定类型的经过编译的代码使得它在某些操作上比普通的 Python列表更加高效一样, Pandas series的类型信息使得它在某些操作上比 Python的字典更高效。用字典创建 Series对象时,其索引默认按照顺序排列。典型的字典数值获取方式仍然有效。和字典不同, Series对象还支持数组形式的操作,比如切片。

创建Series对象

其中,index是一个可选参数,data参数支持多种数据类型。data可以是列表或 NumPy数组,这时 index默认值为整数序列,data也可以是一个标量,创建 Series对象时会重复填充到每个索引上。


data还可以是一个字典,index默认是排序的字典键。每一种形式都可以通过显式指定索引筛选需要的结果,这里需要注意的是, Series对象只会保留显式定义的键值对。

Pandas的 DataFrame对象

DataFrame既可以作为一个通用型 NumPy数组,也可以看作特殊的 Python字典。

Dataframe是通用的 NumPy数组

把 DataFrame看成是有序排列的若干 Series对象。这里的“排
列指的是它们拥有共同的索引。

再结合之前创建的 population的 Series对象,用一个字典创建
个包含这些信息的二维对象



和 Series对象一样, DataFrame也有一个 index属性可以获取索引标签。

另外, DataFrame还有一个 columns属性,是存放列标签的Index对象。

Dataframe可以看作一种通用的 NumPy二维数组,它的行与列都可以通过索引获取。

DataFrame是特殊的字典

与 Series类似,我们也可以把 DataFrame看成一种特殊的字典。字典是一个键映射一个值,而 DataFrame是一列映射一个Series的数据。例如,通过’area’的列属性可以返回包含面积数据的Series对象。

创建DataFrame对象

通过单个Series对象创建

DataFrame是一组 Series对象的集合,可以用单个 Series创建一个单列的 Dataframe。

通过字典列表创建

任何元素是字典的列表都可以变成DataFrame。


即使字典中有些键不存在, Pandas也会用缺失值NaN(不是数字,not a number)来表示。

通过Series对象字典创建

DataFrame也可以用一个由 Series对象构成的字典创建。

通过NumPy二维数组创建

假如有一个二维数组,就可以创建个可以指定行列索引值的 DataFrame。如果不指定行列索引值那么行列默认都是整数索引值。

通过NumPy结构化数组创建

由于 Pandas的 DataFrame与结构化数组十分相似,因此可以通过结
构化数组创建 Dataframe。

Pandas的Index对象

Series和 DataFrame对象都使用便于引用和调整的显式索引。 Pandas的 Index对象是一个很有趣的数据结构,可以将它看作是一个不可变数组或有序集合(实际上是一个多集,因为 Index对象可能会包含重复值)。这两种观点使得 Index对象能呈现一些有趣的功能。让我们用一个简单的整数列表来创建一个 Index对象。

将Index看作不可变数组

Index对象的许多操作都像数组。例如,可以通过标准 Python的取值方法获取数值,也可以通过切片获取数值。


Index对象与 NumPy数组之间的不同在于, Index对象的索引是不可变的,也就是说不能通过通常的方式进行调整。Index对象的不可变特征使得多个DataFrame和数组之间进行索引共享时更加安全,尤其是可以避免因修改索引时粗心大意而导致的副作用。

将Index看作有序集合

Pandas对象被设计用于实现许多操作,如连接(join)数据集,其中会涉及许多集合操作。Index对象遵循 Python标准库的集合(set)数据结构的许多习惯用法,包括并集、交集、差集等。

数据取值与选择

Series数据选择方法

将 Series看作字典

我们还可以用 Python字典的表达式和方法来检测键/索引和值。


Series对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series。

Series对象的可变性是一个非常方便的特性: Pandas在底层已经为可能发生的内存布局和数据复制自动决策,用户不需要担心这些问题。

将 Series看作一维数组

Series不仅有着和字典一样的接口,而且还具备和 NumPy数组一样的数组数据选择功能,包括索引、掩码、花哨的索引等操作。切片是绝大部分混乱之源。需要注意的是,当使用显式索引(即data[‘a’:’c’])作切片时,结果包含最后一个索引;而当使用隐式索引(即data[0:2])作切片时,结果不包含最后一个索引。

索引器:loc、iloc和ix

这些切片和取值的习惯用法经常会造成混乱。例如,如果你的Series是显式整数索引,那么data[1]这样的取值操作会使用显式索引,而data[1:3]这样的切片操作却会使用隐式索引。由于整数索引很容易造成混淆,所以 Pandas提供了一些索引器( indexer)属性来作为取值的方法。它们不是 Series对象的函数方法,而是暴露切片接口的属性。



即从0开始,左闭右开区间。
第三种取值属性是ix,它是前两种索引器的混合形式,在 Series对象中ix等价于标准的取值方式。ix索引器主要用于DataFrame对象。

Python代码的设计原则之一是“显式优于隐式”。使用loc和iloc可以让代码更容易维护,可读性更高。特别是在处理整数索引的对象时,强烈推荐使用这两种索引器。它们既可以让代码阅读和理解起来更容易,也能避免因误用索引/切片而产生的小bug。

DataFrame数据选择方法

Dataframe在有些方面像二维或结构化数组,在有些方面又像一个共享索引的若干 Series对象构成的字典。

将 Dataframe看作字典

两个 Series分别构成 Dataframe的一列,可以通过对列名进行字典形式( dictionary- style)的取值获取数据。


虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame的方法同名,那么就不能用属性索引。例如, Dataframe有一个pop()方法,如果用data.pop就不会获取’pop’列,而是显示为方法。另外,还应该避免对用属性形式选择的列直接赋值(即可以用data[‘pop’]=z,但不要用data.pop=z)。

将DataFrame看作二维数组

可以把 Dataframe看成是一个增强版的二维数组。


理解了这一点,就可以把许多数组操作方式用在 DataFrame上。例如,可以对 DataFrame进行行列转置。
获取一行数据时。

获取一列。

在进行数组形式的取值时,我们就需要用另一种方法。loc、iloc和ix。通过iloc索引器,我们就可以像对待NumPy数组一样索引Pandas的底层数组(Python的隐式索引)

使用ix索引器可以实现一种混合效果。

需要注意的是,ix索引器对于整数索引的处理和之前在 Series对象中介绍的一样,都容易让人混淆。任何用于处理 NumPy形式数据的方法都可以用于这些索引器。

其他取值方法

还有一些取值方法看着有点奇怪,但在实践中还是好用。首先,如果对单个标签取值就选择列,而对多个标签用切边就选择行。


切片也可以不用索引值,而直接用行数来实现。

与此类似,掩码操作也可以直接对每一行进行过滤,而不需要使用loc索引器。

Pandas数值运算方法

NumPy的基本能力之一是快速对每个元素进行运算,既包括基本算术运算(加、减、乘、除),也包括更复杂的运算(三角函数、指数函数和对数函数等)。 Pandas继承了 NumPy的功能,通用函数是关键。

通用函数:保留索引

因为 Pandas是建立在 NumPy基础之上的,所以 NumPy的通用函数同样适用于 Pandas的 Series和 DataFrame对象。

通用函数:索引对齐

当在两个 Series或 DataFrame对象上进行二元计算时, Pandas会在计算过程中对齐两个对象的索引。

Series索引对齐

对于缺失位置的数据, Pandas会用NaN填充,表示“此处无数”。这种索引对齐方式是通过 Python内置的集合运算规则实现的,任何缺失值默认都用NaN填充。


如果用NaN值不是我们想要的结果,那么可以用适当的对象方法代替运算符。例如,A.add(B)等价于A+B,也可以设置参数自定义A或B缺失的数据。

DataFrame索引对齐

在计算两个 DataFrame时,类似的索引对齐规则也同样会出现在共同(并集)列中。


你会发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按顺序排列。

通用函数: DataFrame与 Series的运算

对一个 DataFrame和一个 Series进行计算,行列对齐方式与之前类似。也就是说, DataFrame和 Series的运算规则,与NumPy中二维数组与一维数组的运算规则是一样的。



根据 NumPy的广播规则,让二维数组减自身的行数据会按行计算。在 Pandas里默认也是按行运算的。

如果你想按列计算,那么就需要利用前面介绍过的运算符方法,通过axis参数设置。

处理缺失值

大多数教程里使用的数据与现实工作中的数据的区别在于后者很少是干净整齐的,许多目前流行的数据集都会有数据缺失的现象。更为甚者处理不同数据源缺失值的方法还不同。

选择处理缺失值的方法

在数据表或 DataFrame中有很多识别缺失值的方法。一般情况下可以分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个标签值( sentinel value)表示缺失值。在掩码方法中,掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(0或1)表示有缺失值的局部状态。在标签方法中,标签值可能是具体的数据(例如用9999表示缺失的整数),也可能是些极少出现的形式。另外,标签值还可能是更全局的值,比如用NaN(不是一个数)表示缺失的浮点数,它是IEEE浮点数规范中指定的特殊字符。

使用这两种方法之前都需要先综合考量:使用单独的掩码数组会额外出现一个布尔类型数组,从而增加存储与计算的负担;而标签值方法缩小了可以被表示为有效值的范围,可能需要在CPU或GPU算术逻辑单元中增加额外的(往往也不是最优的)计算逻辑。通常使用的NaN也不能表示所有数据类型。

大多数情况下,都不存在最佳选择,不同的编程语言与系统使用不同的方法。

Pandas的缺失值

Pandas里处理缺失值的方式延续了 NumPy程序包的方式,并没有为浮点数据类型提供内置的NA作为缺失值。

Pandas原本也可以按照R语言采用的比特模式为每一种数据类型标注缺失值,但是这种方法非常笨拙。其工作量几乎相当于创建一个新的NumPy程序包。另外,对于一些较小的数据类型,牺牲一个比特作为缺失值标注的掩码还会导致其数据范围缩小。当然, NumPy也是支持掩码数据的,也就是说可以用一个布尔掩码数组为原数组标注“无缺失值”或“有缺失值”。 Pandas也集成了这个功能,
但是在存储、计算和编码维护方面都需要耗费不必要的资源,因此这种方式并不可取。

综合考虑各种方法的优缺点, Pandas最终选择用标签方法表示缺失值,包括两种 Python原有的缺失值:浮点数据类型的NaN值,以及 Python的None对象。后面我们将会发现,虽然这么做也会有一些副作用,但是在实际运用中的效果还是不错的。

None:Python对象类型的缺失值

Pandas可以使用的第一种缺失值标签是None,它是一个 Python单体对象,经常在代码中表示缺失值。由于None是一个 Python对象,所以不能作为任何 NumPy/ Pandas数组类型的缺失值,只能用于object数组类型。


这里 dtype= object表示 NumPy认为由于这个数组是 Python对象构成的,因此将其类型判断为 object。虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在 Python层面完成,但是在进行常见的快速操作时,这种类型比其他原生类型数组要消耗更多的资源。

使用 Python对象构成的数组就意味着如果你对一个包含None的数组进行累计操作,如sum()或者min(),那么通常会出现类型错误。这就是说,在 Python中没有定义整数与None之间的加法运算。

NaN:数值类型的缺失值

另一种缺失值的标签是NaN(全称 Not a number,不是一个数字),是一种按照IEEE浮点数标准设计、在任何系统中都兼容的特殊浮点数。


NumPy会为这个数组选择一个原生浮点类型,这意味着和之前的 object类型数组不同,这个数组会被编译成C代码从而实现快速操作。你可以把NaN看作是一个数据类病毒它会将与它接触过的数据同化。无论和NaN进行何种操作,最终结果都是NaN。

NumPy也提供了一些特殊的累计函数,它们可以忽略缺失值的影响。

谨记,NaN是一种特殊的浮点数,不是整数、字符串以及其他数据类型。

Pandas中NaN与None的差异

虽然NaN与None各有各的用处,但是 Pandas把它们看成是可以等价交换的,在适当的时候会将两者进行替换。

Pandas会将没有标签值的数据类型自动转换为NA。例如,当我们将整型数组中的一个值设置为np.nan时,这个值就会强制转换成浮点数缺失值NA。除了将整型数组的缺失值强制转换为浮点数, Pandas还会自动将None转换为NaN。


需要注意的是, Pandas中字符串类型的数据通常是用 object类型存储的。

处理缺失值

Pandas基本上把None和NaN看成是可以等价交换的缺失值形式。


返回一个填充看缺失值的数据副本。

发现缺失值

Pandas数据结构有两种有效的方法可以发现缺失值:isnull()和notnull()。每种方法都返回布尔类型的掩码数据。


布尔类型掩码数组可以直接作为 Series或 DataFrame的索引使用。

在 Series里使用的isnull()和notnull()同样适用于Dataframe,产生的结果同样是布尔类型。

剔除缺失值

dropna()(剔除缺失值)和fillna()(填充缺失值)。


我们没法从 DataFrame中单独剔除一个值,要么是剔除缺失值所在的整行,要么是整列。根据实际需求,有时你需要剔除整行,有时可能是整列, DataFrame中的 drona()会有一些参数可以配置。默认情况下, drona()会剔除任何包含缺失值的整行数据。可以设置按不同的坐标轴剔除缺失值,比如axis=1(或axis=‘ columns’)会剔除任何包含缺失值的整列数据。

但是这么做也会把非缺失值一并剔除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置how或 thresh参数来满足,它们可以设置剔除行或列缺失值的数量阈值。

默认设置是how=‘any’,也就是说只要有缺失值就剔除整行或整列(通过axis设置坐标轴)。你还可以设置how=‘all’,这样就只会剔除全部是缺失值的行或列了。


还可以通过 thresh参数设置行或列中非缺失值的最小数量,从而实现更加个性化的配置。

填充缺失值

有时候你可能并不想移除缺失值,而是想把它们替换成有效的数值。有效的值可能是像0、1、2那样单独的值,也可能是经过填充( Imputation)或转换( interpolation)得到的。Pandas为此专门提供了一个fillna()方法,将返回填充了缺失值后的数组副本。


可以用缺失值前面的有效值来从前往后填充。也可以用缺失值后面的有效值来从后往前填充(back-fil)。

Dataframe的操作方法与 Series类似,只是在填充时需要设置坐标轴参数axis。

需要注意的是,假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值。

层级索引

当目前为止,我们接触的都是一维数据和二维数据,用 Pandas的Series和 DataFrame对象就可以存储。但我们也经常会遇到存储多维数据的需求,数据索引超过一两个键。因此, Pandas提供了 Panel和Pane4D对象解决三维数据与四维数据。而在实践中,更直观的形式是通过层级索引( hierarchical indexing,也被称为多级索引,muli- indexing)配合多个有不同等级( level)的一级索引一起使用,这样就可以将高维数组转换成类似一维 Series和二维DataFrame对象的形式。

所及索引Series

笨方法

使用元组。

好方法:Pandas多级索引

用元组表示索引其实是多级索引的基础。Pandas的 MultiIndex类型提供了更丰富的操作方法。我们可以用元组创建一个多级索引。

高维数据的多级索引

unstack()方法可以快速将一个多级索引的 Series转化为普通索引的DataFrame。


当然了,也有stack()方法实现相反的效果。

如果我们可以用含多级索引的一维 Series数据表示二维数据,那么我们就可以用 Series或 Dataframe表示三维甚至更高维度的数据。多级索引每增加一级,就表示数据增加一维,利用这一特点就可以轻松表示任意维度的数据了。假如要增加一列显示每一年各州的人口统计指标(例如18岁以下的人口),那么对于这种带有
Multiindexⅹ的对象,增加一列就像 DataFrame的操作一样简单。

多级索引的创建方法

为 Series或 DataFrame创建多级索引最直接的办法就是将 index参数设置为至少二维的索引数组。



Multiindex的创建工作将在后台完成。同理,如果你把将元组作为键的字典传递给 Pandas, Pandas也会默认转MultiIndex。

显式地创建多级索引

可以通过一个有不同等级的若干简单数组组成的列表来构建 Multiindex。


也可以通过包含多个索引值的元组构成的列表创建 MultiIndex。

还可以用两个索引的笛卡尔积( Cartesian product)创建Multiindex。

更可以直接提供levels(包含每个等级的索引值列表的列表)和labels(包含每个索引值标签列表的列表)创建 MultiIndex。

在创建 Series或 Dataframe时,可以将这些对象作为 index参数,或者通过 reindex方法更新 Series或 Dataframe的索引。

多级索引的等级名称

给MultIindex的等级加上名称会为一些操作提供便利。你可以在前面任何一个 Multiindex构造器中通过 names参数设置等级名称,也可以在创建之后通过索引的names属性来修改名称。

多级列索引

每个 Dataframe的行与列都是对称的,也就是说既然有多级行索引,那么同样可以有多级列索引。

多级索引的取值与切片

Series多级索引

MultIindex也支持局部取值( partial indexing),即只取索引的某一个层级。假如只取最高级的索引,获得的结果是一个新的Series,未被选中的低层索引值会被保留。


类似的还有局部切片,不过要求MultiIndex是按顺序排列的。

如果索引已经排序,那么可以用较低层级的索引取值,第一层级的索引可以用空切片。

其他取值与数据选择的方法也都起作用。下面的例子是通过布尔掩码选择数据。


也可以用花哨的索引选择数据。

DataFrame多级索引

DataFrame多级索引的用法与 Series类似。


由于Dataframe的基本索引是列索引,因此 Series中多级索引的用法到了 DataFrame中就应用在列上了。


与单索引类似,loc、iloc和ix索引器都可以使用。

虽然这些索引器将多维数据当作二维数据处理,但是在loc和iloc中可以传递多个层级的索引元组。

这种索引元组的用法不是很方便,如果在元组中使用切片还会导致语法错误。
虽然可以用Python内置的slice()函数获取想要的切片,但是还可以使用IndexSlice对象,Pandas专门用它解决这些问题。

多级索引行列转换

有序的索引和无序的索引

如果MultiIndex不是有序的索引,那么大多数切片操作都会失败。局部切片和许多其他相似的操作都要求MultiIndex的各级索引是有序的(即按照字典顺序由A至Z)。为此Pandas提供了许多便捷的操作完成排序,如sort_index()和sortlevel()方法。


索引排序之后,局部切片就可以正常使用了。

索引stack与unstack

我们可以将一个多级索引数据集转换成简单的二维形式,可以通过level参数设置转换的索引层级。


unstack()是 stack()的逆操作,同时使用这两种方法让数据保持不变。

索引的设置与重置

层级数据维度转换的另一种方法是行列标签转换,可以通过reset_index方法实现。

多级索引的数据累计方法

对于层级索引数据,可以设置参数level实现对数据子集的累计操作。


需要计算每一年的各项平均值,可以将参数level设置为索引year。

如果再设置axis参数,就可以对列索引进行类似的累计操作了。

合并数据集:Concat与Append操作

使用pd.concat实现简易合并

Pandas有一个pd.concat()函数与 np. concatenate语法类似,但是配置参数更多,功能也更强大。


pd. concat()可以简单地合并一维的 Series或 Dataframe对象,与np. concatenate()合并数组一样。


它也可以用来合并高维数据。

默认情况下,DataFrame的合并都是逐行进行的(默认设置是axis=0)。

这里使用axis=1效果是一样的,但是用axis=‘col’会更直观。

索引重复

np. concatenate与pd. concat最主要的差异之一就是 Pandas在合并时会保留索引,即使索引是重复的!

捕捉索引重复的错误

可以设置verify_integrity参数为True,合并时若有索引重复就会触发异常。

忽略索引

有时索引无关紧要,那么合并时就可以忽略它们,可以通过设置ignore_index参数来实现,如果参数设置为True,那么合并时会创建一个新的整数索引。

增加多级索引

另一种处理方法是通过keys参数为数据源设置多级索引标签。


结果是多级索引的DataFrame。

类似join的合并

实际中,需要合并的数据往往不带有相同的列名。


默认下,某个位置上的缺失会用NaN表示。如果不想这样,可以用join和join_axes参数设置合并方式。默认的合并方式是对所有的输入列进行并集合并(join=‘outer’),当然也可以用join=‘inner’实现对输入列的交集合并。

另一种合并方式是直接确定结果使用的列名,设置 join_axes参数,里面是索引对象构成的列表(是列表的列表)。

append()方法

可以使用df1. append(df2),效果与pd. concat([df1,df2])一样。Pandas的 append()不直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能被称之为一个非常高效的解决方案,因为每次合并都需要重新创建索引和数据缓存。总之,如果你需要进行多个 append操作,还是建议先创建一个 Dataframe列表,然后用 concat()函数一次性解决所有合并任务。

合并数据集:合并与连接

关系代数

pd. merge()实现的功能基于关系代数( relational algebra)的一部分关系代数是处理关系型数据的通用理论,绝大部分数据库的可用操作都以此为理论基础。

数据连接的类型

一对一连接

多对一连接

多对一连接是指,在需要连接的两个列中,有一列的值有重复。通过多对一连接获得的结果 DataFrame将会保留重复值。

多对多连接

设置数据合并的键

参数on的用法

最简单的方法就是直接将参数on设置为一个列名字符串或者一个包含多列名称的列表。这个参数只能在两个 DataFrame有共同列名的时候才可以使用。

left_on与right_on参数

有时你也需要合并两个列名不同的数据集,例如前面的员工信息表中有一个字段不是“ employee’,而是 name”。在这种情况下,就可以用left on和 right on参数来指定列名:


获取的结果中会有一个多余的列,可以通过 Dataframe的drop()方法将这列去掉。

left index与right index参数

除了合并列之外,你可能还需要合并索引。

设置数据连接的集合操作规则

通过前面的示例,我们总结出数据连接的一个重要条件:集合操作规则。当一个值出现在一列,却没有出现在另一列时,就需要考虑集合操作规则了。


可以用how参数设置连接方式,默认值为‘inner’,还有‘outer’、‘left’和‘right’。

重复列名:suffixes参数

由于输出结果中有两个重复的列名,因此pd. merge()函数会自动为它们增加后缀_x或_y,当然也可以通过 suffixes参数自定义后缀名。


suffixes参数同样适用于任何连接方式,即使有三个及三个以上的重复列名时也同样适用。

累计与分组

在对较大的数据进行分析时,一项基本的工作就是有效的数据累计( summarization):计算累计( aggregation)指标,如sum()、mean()、 median()、min()和max(),其中每一个指标都呈现了大数据集的特征。

Pandas的简单累计功能

Pandas的 Series和 DataFrame支持所有24节中介绍的常用累计函数。另外,还有一个非常方便的 describe()方法可以计算每一列的若干常用统计值。

DataFrame和 Series对象支持以上所有方法。

GroupBy:分割、应用和组合

分割、应用和组合

GroupBy的用处就是将这些步骤进行抽象:用户不需要知道在底层如何计算,只要把操作看成一个整体就够了。

我们可以用 DataFrame的 groupby()方法进行绝大多数常见的分割-应用-组合操作,将需要分组的列名传进去即可。


需要注意的是,这里的返回值不是一个 DataFrame对象,而是个 DataFrame GroupBy对象。这个对象的魔力在于,你可以将它看成是一种特殊形式的 DataFrame,里面隐藏着若干组数据,但是在没有应用累计函数之前不会计算。

sum()只是众多可用方法中的一个。你可以用 Pandas或 NumPy的任意一种累计函数,也可以用任意有效的 DataFrame对象。

GroupBy对象

GroupBy对象是一种非常灵活的抽象类型。在大多数场景中,你可以将它看成是 Dataframe的集合,在底层解决所有难题。

按列取值

GroupBy对象与 Dataframe一样,也支持按列取值,并返回一个修改过的 GroupBy对象。

这里从原来的 DataFrame中取某个列名作为一个 Series组。与GroupBy对象一样,直到我们运行累计函数,才会开始计算。

按组迭代

GroupBy对象支持直接按组进行迭代,返回的每组都是 Series或 Dataframe。

调用方法

借助 Python类的魔力(@ classmethod),可以让任何不由 GroupBy对象直接实现的方法直接应用到每一组,无论是 Dataframe还是 Series对象都同样适用。

累计、过滤、转换和应用

累计

aggregate()可以支持更复杂得操作。比如字符串、函数或者函数列表,并且能一次性计算所有累计值。



另一种用法是通过Python字典指定不同列需要累计的函数。

过滤

过滤操作可以让呢按照分组的属性丢弃若干数据。


转换

累计操作返回的是对组内全量数据缩减过的结果,而转换操作会返回一个新的全量数据。数据经过转换之后,其形状与原来的输入数据是一样的。

apply()方法

apply()方法让你可以在每个组上应用任意方法。这个函数输入一个 DataFrame,返回一个 Pandas对象( Data Frame或 Series)或一个标量( scalar,单个数值)。

设置分割的键

将列表、数组、Series或索引作为分组键

用字典或Series将索引映射到分组名称

任意Python函数

多个有效键构成的列表

数据透视表

向量化字符串操作

Pandas字符串操作简介

Pandas为包含字符串的 Series和 Index对象提供的str属性堪称两全其美的方法,它既可以满足向量化字符串操作的需求,又可以正确地处理缺失值。例如,我们用前面的数据data创建了一个 Pandas的Series。


可以直接调用转换大写方法capitalize()将所有的字符串变成大写形式,缺失值会被跳过。

Pandas字符串方法列表

与Python字符串方法相似的方法

注意,这些方法的返回值不同。有的返回一个字符串Series,有的返回数值,有的返回布尔值,有的返回列表或其他复合值。

使用正则表达式的方法

还可以用正则表达式中的开始符号(\^)与结尾符号(\$)来实现。能将正则表达式应用到Series与DataFrame之中的话,就有可能实现更多的数据分析与清洗方法。

其他字符串方法

处理时间序列

Pandas最初是为金融模型而创建的,因此它拥有一些功能非常强大的日期、时间、带时间索引数据的处理工具。

时间戳表示某个具体的时间点(例如2015年7月4日上午7点)。

时间间隔与周期表示开始时间点与结束时间点之间的时间长度,例如2015年(指的是2015年1月1日至2015年12月31日这段时间间隔)。周期通常是指一种特殊形式的时间间隔,每个间隔长度相同,彼此之间不会重叠(例如,以24小时为周期构成每天)。

时间增量( time delta)或持续时间( duration)表示精确的时间长度(例如,某程序运行持续时间2256秒)。

Python的日期与时间工具

原生Python的日期与时间工具:datetime与dateutil

datetime和 dateuti1模块在灵活性与易用性方面都表现出色你可以用这些对象及其相应的方法轻松完成你感兴趣的任意操作但如果你处理的时间数据量比较大,那么速度就会比较慢。就像之前介绍过的 Python的原生列表对象没有 NumPy中已经被编码的数值类型数组的性能好一样, Python的原生日期对象同样也没有NumPy中已经被编码的日期( encoded dates)类型数组的性能好。

时间类型数组:NumPy的datetime64类型

Python原生日期格式的性能弱点促使 NumPy团队为 NumPy增加了自己的时间序列类型。 datetime64类型将日期编码为64位整数,这样可以让日期数组非常紧凑(节省内存)。datetime64需要在设置日期时确定具体的输入类型。


只要有了这个日期格式,就可以快速的向量化运算。

虽然 datetime64弥补了 Python原生的datetime类型的不足,但它缺少了许多 datetime(尤其是dateutil)原本具备的便捷方法与函数。

Pandas的日期与时间工具:理想与现实的最佳解决方案

Pandas所有关于日期与时间的处理方法全部都是通过 Timestamp对象实现的,它利用 numpy. datetime64的有效存储和向量化接口将 datetime和 dateutil的易用性有机结合起来。 Pandas通过一组 Timestamp对象就可以创建一个可以作为 Series或DataFrame索引的 Datetimelndex。

Pandas时间序列:用时间作索引

Pandas时间序列工具非常适合用来处理带时间戳的索引数据。我们可以通过一个时间索引数据创建一个 Series对象。


有了一个带时间索引的Series之后,就能用它来演示之前介绍过的Series取值方法,可以直接用日期进行切片取值。

另外,还有一些仅在此类 Series上可用的取值操作,例如直接通过年份切片获取该年的数据。

Pandas时间序列数据结构

  1. 针对时间戳数据, Pandas提供了 Timestamp类型。与前面介绍的一样,它本质上是 Python的原生 datetime类型的替代品,但是在性能更好的 numpy.datetime64类型的基础上创建。对应的索引数据结构是Datetimeindex。
  2. 针对时间周期数据, Pandas提供了 Period类型。这是利用numpy. datetime64类型将固定频率的时间间隔进行编码。对应的索引数据结构是 Periodindex。
  3. 针对时间增量或持续时间, Pandas提供了 Timedelta类型。Timedelta是一种代替 Python原生 datetime, timedelta类型的高性能数据结构,同样是基于 numpy. timedelta64类型。对应的索引数据结构是 Timedeltaindex。

最基础的日期/时间对象是 Timestamp和 Datetimeindex。这两种对象可以直接使用,最常用的方法是pd.to_datetime()函数,它可以解析许多日期与时间格式。对pd.to_datetime()传递一个日期会返回一个 Timestamp类型,传递一个时间序列会返回一个Datetimelndex类型。



任何Datetimeindex类型都可以通过to_ period()方法和一个频率代码转换成 Periodindex类型。下面用‘D’将数据转换成单日的时间序列。

当用一个日期减去另一个日期时,返回的结果是 Timedeltaindex类型。

有规律的时间序列:pd.date_range()

为了能更简便地创建有规律的时间序列, Pandas提供了一些方法:pd.daterange()可以处理时间戳、pd. period range()可以处理周期、pd.timedelta range()可以处理时间间隔。我们已经介绍过, Python的 range()和NumPy的 np.arange()可以用起点、终点和步长(可选的)创建一个序列。pd.daterange()与之类似,通过开始日期、结束日期和频率代码(同样是可选的)创建一个有规律的日期序列,默认的频率是天。


此外,日期范围不一定非是开始时间与结束时间,也可以是开始时间与周期数periods。

你可以通过freq参数改变时间间隔,默认值是D。例如,可以创建个按小时变化的时间戳。

如果要创建一个有规律的周期或时间间隔序列,有类似的函数pd.period_range()和pd. timedelta_range()。下面是一个以月为周期的示例。

一个以小时递增的序列。

时间频率与偏移量

可以在频率代码后面加三位月份缩写字母来改变季、年频率的开始时间。


还可以将频率组合起来创建新的周期。

重新取样、迁移和窗口

重新取样与频率转换

处理时间序列数据时,经常需要按照新的频率(更高频率、更低频率)对数据进行重新取样。你可以通过 resample()方法解决这个问题,或者用更简单的 asfreq()方法。这两个方法的主要差异在于, resample()方法是以数据累计( data aggregation)为基础,而 asfreq()方法是以数据选择( data selection)为基础。

时间迁移

一种常用的时间序列操作时对数据按时间进行迁移。Pandas有两种解决这类问题的方法:shift()和tshift()。简单来说,shift()就是迁移数据,而tshift()就是迁移索引。两种方法都是按照频率代码进行迁移。


shift(900)将数据向前推进了900天,而tshift(900)将时间索引向前推进了900天。

移动时间窗口

高性能 Pandas:eval()与 query()

query()与eval()的设计动机:复合代数式

NumPy与Pandas都支持快速的向量化运算。

用pandas.eval()实现高性能运算

Pandas的eval()函数用字符串代数式实现了 DataFrame的高性能运算。eval()版本的代数式比普通方法快一倍(而且内存消耗更少),结果也是一样的。

pd.eval()支持的运算

算术运算符

pd.eval()支持所有的算术运算符。

比较运算符

pd.eval()支持所有的比较运算符,包括链式代数式。

位运算符

对象属性与索引

其他运算

目前pd.eval()还不支持函数调用、条件语句、循环以及更复杂的运算。如果你想要进行这些运算,可以借助 Numexpr来实现。

用 DataFrame.eval()实现列间运算

由于 pd. eval()是 Pandas的顶层函数,因此 Dataframe有一个eval()方法可以做类似的运算。使用eval()方法的好处是可以借助列名称进行运算。



Dataframe.eval()方法可以通过列名称实现简洁的代数式。

用 DataFrame. eval()新增列

Dataframe.eval()使用局部变量

Dataframe.eval()方法还支持通过@符号使用 Python的局部变量。


@符号表示“这是一个变量名称而不是一个列名称”,从而让你灵活地用两个“命名空间”的资源(列名称的命名空间和 Python对象的命名空间)计算代数式。需要注意的是,@符号只能在Dataframe.eval()方法中使用,而不能在 pandas.eval()函数中使用,因为 pandas. eval()函数只能获取一个( Python)命名空间的内容。

DataFrame.query()方法

除了计算性能更优以外,这种方法的语法也比掩码代数式语法更好理解。需要注意的是,query()方法也支持@符号引用局部变量。

性能决定使用时机

在考虑要不要用这两个函数时,需要思考两个方面:计算时间和内存消耗,而内存消耗是更重要的影响因素。就像前面介绍的那样,每个涉及NumPy数组或Pandas的 DataFrame的复合代数式都会产生临时数组。


如果临时 DataFrame的内存需求比你的系统内存还大(通常是几吉字),那么最好还是使用eval()和 query()代数式。

在性能方面,即使没有使用最大的系统内存,eval()的计算速度也比普通方法快。在实际工作中,我发现普通的计算方法与eval/ query计算方法在计算时间上的差异并非总是那么明显,普通方法在处理较小的数组时反而速度更快!eval/ query方法的优点主要是节省内存,有时语法也更加简洁。